//
// Created by china on 25-1-4.
//

#include "ChatService.h"

ChatService::ChatService() {
    _msgHandlerMap.insert({ReqType::Logic, std::bind(&ChatService::logic, this, _1, _2, _3)});
    _msgHandlerMap.insert({ReqType::Register, std::bind(&ChatService::regist, this, _1, _2, _3)});
    _msgHandlerMap.insert({ReqType::OneChat, std::bind(&ChatService::oneChat, this, _1, _2, _3)});
    _msgHandlerMap.insert({ReqType::AddFriend, std::bind(&ChatService::addFriend, this, _1, _2, _3)});
    _msgHandlerMap.insert({ReqType::CreatGroup, std::bind(&ChatService::creatGroup, this, _1, _2, _3)});
    _msgHandlerMap.insert({ReqType::AddGroup, std::bind(&ChatService::addGroup, this, _1, _2, _3)});
    _msgHandlerMap.insert({ReqType::GroupChat, std::bind(&ChatService::groupChat, this, _1, _2, _3)});

    //连接redis服务器
    if(_redis.connect()){
        _redis.initNotifyHandler(std::bind(&ChatService::handleRedisSubscribeMessage,this,_1, _2));
    }
}

//获取单例
ChatService *ChatService::getInstance() {
    static ChatService service;
    return &service;
}

//处理登录业务
void ChatService::logic(const TcpConnectionPtr &conn, nlohmann::json &js, Timestamp time) {
    LOG_INFO << "ChatService::logic";

    std::string str = to_string(js);
    size_t nposID = str.find("userID");
    size_t nposPassword = str.find("password");
    if(nposID == std::string::npos || nposPassword == std::string::npos){
        LOG_ERROR<<"没有找到 userID 或 password 字段!";
        return;
    }

    nlohmann::json response;
    int id = js["userID"];
    string pwd = js["password"];

    struct MySQLTable::User user  = _userModel.query(id);
    if(pwd == user.password && id == user.id){//查找成功
        if(user.state == "offline"){//用户未登录
            //更新用户状态
            user.state = "online";
            if(_userModel.updateState(user)){
                response["error"] = ResType::NoError;//表示成功
                response["userid"] = user.id;
                response["name"] = user.name;

                //上锁，用智能指针管理该锁
                lock_guard<std::mutex> lock(_connMutex);
                //将用户id与连接建立映射
                _userConnMap.insert({user.id, conn});
            }
            //查询用户是否有离线消息
            std::vector<std::string> offlineMsgVec = getOfflineMsg(user);
            response["offlineMsg"] = offlineMsgVec;

            //获取用户好友关系
            std::vector<std::string> friendsVec = getFriends(user);
            response["friends"] = friendsVec;

            //获取用户群组信息
            std::vector<std::string> groupMsgVec = getGroupMsg(user);
            response["groupMsg"] = groupMsgVec;

            //在 redis 上订阅channel（id）
            _redis.subscribe(id);

        }else{//用户已登录
            response["error"] = ResType::OnlineError;//表示已在线
            response["errorMsg"]="用户已登录";
        }
    }else{//查找失败
        response["error"] = ResType::NameOrPasswordError;//表示用户名或密码错误
        response["errorMsg"]="用户名或密码错误";
    }
    response["msgID"] = ResType::LogicAck;
    conn->send(response.dump());//返回给客户端
}

//处理注册业务
void ChatService::regist(const TcpConnectionPtr &conn, nlohmann::json &js, Timestamp time) {
    LOG_INFO << "ChatService::regist";

    size_t nposName = to_string(js).find("name");
    size_t nposPassword = to_string(js).find("password");
    if(nposName == std::string::npos || nposPassword == std::string::npos){
        LOG_ERROR<<"没有找到 name 或 password 字段!";
        return;
    }

    nlohmann::json response;
    struct MySQLTable::User user;
    user.name = js["name"];
    user.password = js["password"];

    if(_userModel.insert(user)){//注册成功
        response["error"] = ResType::NoError;//表示注册成功
        response["userID"] = user.id;
        response["name"] = user.name;
    }else{//注册失败
        response["error"] = ResType::RegisterError;//表示注册失败
        response["errorMsg"]="注册失败";
    }
    response["msgID"] = ResType::RegisterAck;
    conn->send(response.dump());//返回给客户端
}

//处理私聊业务
void ChatService::oneChat(const TcpConnectionPtr &conn,nlohmann::json &js,Timestamp time){
    LOG_INFO<<"ChatService::oneChat";

    std::string str = to_string(js);
    ssize_t nposToID = str.find("toID");
    ssize_t nposFromID = str.find("fromID");
    if(nposToID == std::string::npos || nposFromID == std::string::npos){
        LOG_ERROR<<"没有找到 toID 或 nposFromID 字段!";
        return ;
    }

    //创建离线用户
    struct MySQLTable::OfflineMessage offline;

    //查询 fromID 是否在线
    int fromID = js["fromID"];
    struct MySQLTable::User user = _userModel.query(fromID);
    if(user.state != "online"){
        LOG_ERROR<<"发送方 "<<user.name<<" 不在线!";
        return ;
    }

    int toID = js["toID"].get<int>();

    //涉及到找寻 _userConnMap 中的内容，上锁保证安全，同时为了避免在发送消息的时候客户端退出了，将发送消息的过程一起上锁
    {
        lock_guard<mutex> lock(_connMutex);
        auto it = _userConnMap.find(toID);
        if(it != _userConnMap.end()){
            //toID在本台服务器上面
            it->second->send(js.dump());
            return;
        }
    }

    //toID对应的客户不在本台服务器，查询该客户端是否在线
    auto testUser = _userModel.query(toID);
    if(testUser.state == "online"){//在线，在别的服务器
        _redis.publish(toID,js.dump());
        return;
    }
    //客户端不在线，存储离线消息
    offline.userid = toID;
    offline.message = js.dump();
    _offlineMessageModel.insert(offline);
}

//处理添加好友业务
void ChatService::addFriend(const TcpConnectionPtr &conn,nlohmann::json &js,Timestamp time){
    LOG_INFO<<"ChatService::addFriend";

    std::string str = to_string(js);
    ssize_t nposUserID = str.find("userID");
    ssize_t nposFriendID = str.find("friendID");
    if(nposUserID == std::string::npos || nposFriendID == std::string::npos){
        LOG_ERROR<<"没有找到 userid 或 friendID 字段!";
        return ;
    }

    struct MySQLTable::User user = _userModel.query(js["userID"]);
    if(user.state == "offline"){
        LOG_ERROR<<"用户不在线";
        return;
    }

    struct MySQLTable::Friend addFriend;
    addFriend.userid = js["userID"];
    addFriend.friendid = js["friendID"];
    _addFriendModel.instert(addFriend);
}

//处理创建群业务
void ChatService::creatGroup(const TcpConnectionPtr &conn,nlohmann::json &js,Timestamp time){
    LOG_INFO<<"ChatService::creatGroup";

    std::string str = to_string(js);
    ssize_t npos1 = str.find("userID");
    ssize_t npos2 = str.find("groupName");
    ssize_t npos3 = str.find("groupDesc");
    if(npos1 == std::string::npos || npos2 == std::string::npos || npos3 == std::string::npos){
        LOG_ERROR<<"没有找到 userid 或 friendID 或 groupDesc 字段!";
        return ;
    }

    int userid = js["userID"];
    string name = js["groupName"];
    string desc = js["groupDesc"];
    struct MySQLTable::AllGroup group;
    group.name = name;
    group.desc = desc;
    if(_groupModel.createGroup(group)){
        struct MySQLTable::GroupUser groupUser;
        groupUser.user.id = userid;
        groupUser.groupid = group.id;
        groupUser.role = "creator";
        _groupModel.addGroup(groupUser);
    }
}

//处理添加群业务
void ChatService::addGroup(const TcpConnectionPtr &conn,nlohmann::json &js,Timestamp time){
    LOG_INFO<<"ChatService::creatGroup";

    std::string str = to_string(js);
    ssize_t npos1 = str.find("userID");
    ssize_t npos2 = str.find("groupID");
    if(npos1 == std::string::npos || npos2 == std::string::npos ){
        LOG_ERROR<<"没有找到 userID 或 groupID  字段!";
        return ;
    }

    int userid = js["userID"];
    int groupid = js["groupID"];
    struct MySQLTable::GroupUser groupUser;
    groupUser.user.id = userid;
    groupUser.groupid = groupid;
    groupUser.role = "normal";
    _groupModel.addGroup(groupUser);
}

//处理群聊天业务
void ChatService::groupChat(const TcpConnectionPtr &conn,nlohmann::json &js,Timestamp time){
    LOG_INFO<<"ChatService::creatGroup";

    std::string str = to_string(js);
    ssize_t npos1 = str.find("userID");
    ssize_t npos2 = str.find("groupID");
    if(npos1 == std::string::npos || npos2 == std::string::npos ){
        LOG_ERROR<<"没有找到 userID 或 groupID  字段!";
        return ;
    }

    int userid = js["userID"];
    int groupid = js["groupID"];
    std::vector<int> userIDVec = _groupModel.queryGroupUsers(userid,groupid);
    for(int id: userIDVec){
        lock_guard<mutex> lock(_connMutex);
        auto it = _userConnMap.find(id);
        if(it != _userConnMap.end()){//在本台服务器上，直接发送
            it->second->send(js.dump());
        }else{//不在本台电脑上
            //查询是否在线
            auto testUser = _userModel.query(id);
            if(testUser.state == "online"){//在线，向redis发送消息
                _redis.publish(id,js.dump());
            }else{//不在线
                struct MySQLTable::OfflineMessage offlineMessage;
                offlineMessage.userid = id;
                offlineMessage.message = js.dump();
                _offlineMessageModel.insert(offlineMessage);
            }
        }
    }
}

//获取离线消息
std::vector<std::string> ChatService::getOfflineMsg(const struct MySQLTable::User& user){
    LOG_INFO<<"ChatService::getOfflineMsg";
    //用于获取离线消息
    std::vector offlineMessageVec = _offlineMessageModel.query(user);
    if(!offlineMessageVec.empty()){
        //删除数据库中的离线消息
        _offlineMessageModel.remove(user);
    }
    return offlineMessageVec;
}

//获取好友关系
std::vector<std::string> ChatService::getFriends(const struct MySQLTable::User& user){
    LOG_INFO<<"ChatService::getFriends";
    std::vector<string> vec;
    std::vector<struct MySQLTable::User> friendVec = _addFriendModel.query(user);
    if(!friendVec.empty()){
        for(MySQLTable::User it : friendVec){
            nlohmann::json jsStr;
            jsStr["id"] = it.id;
            jsStr["name"] = it.name;
            jsStr["state"] = it.state;
            vec.push_back(jsStr.dump());
        }
    }
    return vec;
}

//获取群聊消息
std::vector<std::string> ChatService::getGroupMsg(const struct MySQLTable::User& user){
    LOG_INFO<<"ChatService::getGroupMsg";
    std::vector<struct MySQLTable::AllGroup> allGroupVec = _groupModel.queryGroups(user);
    std::vector<std::string> groupV;
    if(!allGroupVec.empty()){
        for(struct MySQLTable::AllGroup group : allGroupVec){//取出每个群
            nlohmann::json groupJson;
            groupJson["userid"] = group.id;
            groupJson["groupname"] = group.name;
            groupJson["groupdesc"] = group.desc;
            std::vector<std::string> userV;
            for(struct MySQLTable::GroupUser gUser : group.users){//取出群里每个成员
                nlohmann::json userJson;
                userJson["id"] = gUser.user.id;
                userJson["name"] = gUser.user.name;
                userJson["state"] = gUser.user.state;
                userJson["role"] = gUser.role;
                userV.push_back(userJson.dump());
            }
            groupJson["users"]=userV;
            groupV.push_back(groupJson.dump());
        }
    }
    return groupV;
}

//处理 redis 订阅
void ChatService::handleRedisSubscribeMessage(int userid,std::string msg){
    LOG_INFO<<"ChatService::handleRedisSubscribeMessage";
    lock_guard<mutex> lock(_connMutex);
    auto it = _userConnMap.find(userid);
    if(it != _userConnMap.end()){
        it->second->send(msg);
        return;
    }
    //若接收消息的时候刚好下线，存储离线消息
    struct MySQLTable::OfflineMessage offlineMessage;
    offlineMessage.userid = userid;
    offlineMessage.message=msg;
    _offlineMessageModel.insert(offlineMessage);
}

//处理客户端退出业务
void ChatService::clientCloseExcption(const TcpConnectionPtr &conn) {
    LOG_INFO<<"ChatService::clientCloseExcption";

    MySQLTable::User user;

    {
        //上锁，用智能指针管理该锁,保证操作 _userConnMap 安全
        lock_guard<std::mutex> lock(_connMutex);
        for(auto it = _userConnMap.begin(); it != _userConnMap.end(); ++it){
            if(it->second == conn){
                user.id = it->first;
                _userConnMap.erase(it);
                break;
            }
        }
    }

    //保证是有效的用户
    if(user.id != -1){
        //更新客户端状态
        user.state = "offline";
        _userModel.updateState(user);
    }

    //取消 redis 上的订阅
    _redis.unsubscibe(user.id);
}

//处理服务器退出事件
void ChatService::reset(){
    LOG_INFO<<"ChatService::reset";
    _userModel.resetUserState();
}

//获取消息对应的处理器
MsgHandler ChatService::getHandler(const ReqType& msgID) {
    auto it = _msgHandlerMap.find(msgID);
    if(it == _msgHandlerMap.end()){
        return [](const TcpConnectionPtr& conn,nlohmann::json& js,Timestamp time){
            //返回一个空处理
            LOG_ERROR<<"没找到对应的处理方法";
        };
    } else{
        return _msgHandlerMap[msgID];
    }
}



